#include "SearchClient.h"
#include "Common.h"


namespace cm_nat
{
    SearchClient::SearchClient(ByteBufferPool* p_pool, SocketReactor* reactor, BaseTimer* p_timer)
        : m_bufferPool(p_pool), m_reactor(reactor), m_timer(p_timer),m_port(NULL),m_listener(NULL),
        m_connectDaemon(NULL)
    {

    }

    SearchClient::~SearchClient()
    {

    }

    int SearchClient::setup( IPAddress bindIP )
    {
        if( !m_port ){
            m_port = new MulticastPort( *m_reactor, m_bufferPool );
        }
        m_port->setup(MulticastPort::MULTICAST_CLIENT,bindIP, 
            SocketAddress(MULTICAST_GROUP_IP, MULTICAST_GROUP_PORT) );

        m_port->registerListener( this );

        return 0;
    }

    int SearchClient::teardown()
    {
        return 0;
    }

    void SearchClient::registerListener( SearchClientListener* p_listener )
    {
        m_listener = p_listener;
    }

    void SearchClient::setMediaAddress( SocketAddress mediaAddress )
    {
        m_clientMediaAddress = mediaAddress;
    }

    int SearchClient::startSearch()
    {
        StunMessage request;
        request.SetType(STUN_BINDING_REQUEST);

        ByteBuffer* p_data = NULL;
        if(m_bufferPool){
            p_data = m_bufferPool->get(0);
        }else{
            return false;
        }
        if( !p_data ){
            return false;
        }
        p_data->increaseRef();
        request.Write( p_data );

        m_port->send(p_data, SocketAddress(MULTICAST_GROUP_IP, MULTICAST_GROUP_PORT) );

        m_timer->start(5000,TIMER_SEARCH_DAEMON, this);

        return true;
    }

    int SearchClient::authenticate( std::string username, std::string pwd )
    {    
        m_connectDaemon = findDaemon( username );
        if( !m_connectDaemon ){
            return 0;
        }

        StunMessage request;
        request.SetType(STUN_SEND_REQUEST);

        StunAddressAttribute* addr_attr = 
            StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);

        addr_attr->SetFamily(1);
        addr_attr->SetPort( m_clientMediaAddress.port() );

        //must get media_ip in a temp var,or else the returned in_addr is invalid
        IPAddress media_ip = m_clientMediaAddress.host();
        const struct in_addr* ip= (struct in_addr*)media_ip.addr();
        addr_attr->SetIP( ByteOrder::fromNetwork( (UInt32)ip->s_addr ) );
        request.AddAttribute(addr_attr);

        StunByteStringAttribute* id_attr = 
            StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
        id_attr->CopyBytes( username.c_str(), username.length() );
        request.AddAttribute( id_attr );

        StunByteStringAttribute* pwd_attr = 
            StunAttribute::CreateByteString(STUN_ATTR_PASSWORD);
        pwd_attr->CopyBytes( pwd.c_str(), pwd.length() );
        request.AddAttribute( pwd_attr );

        ByteBuffer* p_data = NULL;
        if(m_bufferPool){
            p_data = m_bufferPool->get(0);
        }else{
            return false;
        }
        if( !p_data ){
            return false;
        }
        p_data->increaseRef();
        request.Write( p_data );

        m_port->send(p_data, SocketAddress( m_connectDaemon->sa.host(), MULTICAST_GROUP_PORT) );
        
        return 0;
    }

    const SearchDaemonInfo* SearchClient::findDaemon( std::string username )
    {
        SearchDaemonList::const_iterator it = m_foundDaemons.begin();
        for( ; it != m_foundDaemons.end(); ++it ){
            if( (*it)->username == username ){
               return (*it);
            }
        }
        return NULL;
    }

    bool SearchClient::detectMessage(ByteBuffer* data)
    {
        if(data->Length()<20){
            return false;
        }

        const char* p_data = data->Data();
        char byte = p_data[0];

        if( byte & 0xc0 ){
            return false;
        }

        //message length is 4 aligns
        byte = p_data[3];
        if( byte & 0x03 ){
            //            return false;
        }

        return true;
    }

    int SearchClient::processMessage( ByteBuffer* data )
    {
        bool res = false;
        StunMessage smsg;
        res = smsg.Read( data );

        const SocketAddress* from = (SocketAddress*)data->getData();

        poco_debug(g_log_nat, Logger::format("processMessage from address:$0", from->toString()) );

        switch( smsg.type() )
        {
        case STUN_BINDING_RESPONSE:
            {
                processSearchResponse( &smsg, *from );
            }
            break;
        case STUN_SEND_RESPONSE:
            {
                processAuthResponse( &smsg, *from );
            }
            break;
        default:
            break;
            
        }

        return CM_RES_SUCCESS;
    }

    int SearchClient::processSearchResponse( StunMessage* p_sm, const SocketAddress& from )
    {
        bool res = false;
        std::string username;

        const StunAddressAttribute* p_ma = p_sm->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
        if( !p_ma ){
            return false;
        }

        struct in_addr  ip;
        ip.s_addr = ByteOrder::toNetwork( (UInt32)p_ma->ip() );
        SocketAddress sa( IPAddress(&ip, sizeof(in_addr)), p_ma->port() );

        const StunByteStringAttribute* p_name_a = p_sm->GetByteString(STUN_ATTR_USERNAME);
        if( !p_name_a ){
            return false;
        }
        username = std::string( p_name_a->bytes(), p_name_a->length() );

        poco_debug(g_log_nat,Logger::format("processSearchResponse$0 : $1", sa.toString(), username));

        SearchDaemonInfo* p_sdi = new SearchDaemonInfo;
        p_sdi->sa = sa;
        p_sdi->username = username;
        m_foundDaemons.push_back( p_sdi );

        return CM_RES_SUCCESS;
    }

    int SearchClient::processAuthResponse( StunMessage* p_sm, const SocketAddress& from )
    {
        const StunAddressAttribute* p_ma = p_sm->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
        if( !p_ma ){
            //auth fail
            if( m_listener ){
                m_listener->onAuthResult( this, AUTH_RES_FAIL );
            }
            return CM_RES_SUCCESS; 
        }

        struct in_addr  ip;
        ip.s_addr = ByteOrder::toNetwork( (UInt32)p_ma->ip() );
        SocketAddress sa( IPAddress(&ip, sizeof(in_addr)), p_ma->port() );
        m_daemonMediaAddress = sa;
        if( m_listener ){
            m_listener->onAuthResult( this, AUTH_RES_SUCCESS );
        }

        return CM_RES_SUCCESS;
    }
    
    void SearchClient::onReceive(MulticastPort*p_port, ByteBuffer* p_data)
    {
        poco_debug( g_log_nat, "SearchClient::onReceive");
        std::cout<<"length:"<<p_data->Length()<<std::endl;

        if( detectMessage(p_data) ){
            processMessage(p_data);
        }

        SocketAddress* p_sa = (SocketAddress*)p_data->getData();
        delete p_sa;
        p_data->decreaseRef();
        
    }

    void SearchClient::onTimer( TimerNotification* p_tn )
    {
        if(! p_tn ){
            return;
        }

        switch( p_tn->getContext() )
        {
        case TIMER_SEARCH_DAEMON:
            {
                if(m_listener){
                    m_listener->onSearchFinish( this );
                }
            }
            break;
        default:
            break;
        }
    }

    void SearchClient::startTimer(long interval, UInt32 context)
    {
        m_timer->start(interval, context, this);
    }
}